package communication;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;

import communication.definitions.Message;
import communication.exceptions.InvalidArgumentsException;
import communication.exceptions.InvalidFunctionException;

import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**
 * The <code>Communicator</code> class is used for communications between the
 * robot and the client. All communication follows the given protocol.
 * <p>
 * <b>The Protocol</b>
 * <p>
 * The protocol was designed to make it extremely easy to add new functions to
 * the robot. With this protocol it is possible to have multiple functions for
 * one device which take the same number of arguments. In addition, the
 * functions on the robot side do not need to take an unnecessary number of
 * arguments. Since the protocol was designed this way there was the possibility
 * that the robot and client become out of sync and both start executing
 * garbage; to reduce the change that this happens a KNOCK byte (0xFF) begins
 * each message. This warns the client or robot that is about to receive a
 * message.
 * <p>
 * <code><pre>
 *         CLIENT                           ROBOT<br>
 *         KNOCK,2,ROBOT,0,START_ROBOT      ->Receives message and<br>
 *                                              executes start_robot<br>
 *         KNOCK,2,ADC,1,REQUEST_VALUE      ->Receives message and<br>
 *                                              calculates ADC value (0x07D3)<br>
 *         ->Receives message and           KNOCK,4,ADC,1,SET_VALUE,0x07,0xD3<br>
 *             sets ADC value<br>
 * </pre></code>
 * <p>
 * The byte following KNOCK is tells how many bytes are to follow. If the two
 * become out of sync they should correct themselves once the next KNOCK byte is
 * sent.
 * <p>
 * This class is based off of examples found on Sun's website: <a
 * href="http://java.sun.com/developer/releases/javacomm/SimpleRead.java">
 * SimpleRead.java</a>, and <a
 * href="http://java.sun.com/developer/releases/javacomm/SimpleWrite.java">
 * SimpleWrite.java</a>
 * 
 * @author Peter Kalauskas
 */
public class Communicator implements SerialPortEventListener {
	public static final byte KNOCK = (byte) 0xFF;

	Dispatcher dispatcher;
	CommPortIdentifier portID;

	InputStream inputStream;
	OutputStream outputStream;
	SerialPort serialPort;
	Thread readThread;

	/**
	 * Constructs a new communicator object on the given port.
	 * 
	 * @param port
	 *            the port to attempt to connect to
	 * @param d
	 *            the dispatcher object which will be used to pass along
	 *            received messages
	 */
	public Communicator(String port, Dispatcher d) {
		boolean portFound = false;
		dispatcher = d;

		Enumeration portList = CommPortIdentifier.getPortIdentifiers();

		// Loop through the ports until we find the correct port
		while (portList.hasMoreElements() && portFound == false) {
			portID = (CommPortIdentifier) portList.nextElement();
			if (portID.getPortType() == CommPortIdentifier.PORT_SERIAL) {
				if (portID.getName().equals(port)) {
					System.out.println("Found port: " + port);
					portFound = true;
				}
			}
		}
		if (!portFound) {
			System.out.println("port " + port + " not found.");
		}

		try {
			serialPort = (SerialPort) portID.open("CommunicatorIO", 2000);
		} catch (PortInUseException e) {
		}

		try {
			inputStream = serialPort.getInputStream();
			outputStream = serialPort.getOutputStream();
		} catch (IOException e) {
		}

		try {
			serialPort.addEventListener(this);
		} catch (TooManyListenersException e) {
		}

		serialPort.notifyOnDataAvailable(true);

		try {
			serialPort.setSerialPortParams(19200, SerialPort.DATABITS_8,
											SerialPort.STOPBITS_1,
											SerialPort.PARITY_NONE);
		} catch (UnsupportedCommOperationException e) {
		}
	}

	/**
	 * The interrupt handler for this communicator. This will be called every
	 * time a byte is added to the input stream. This is the function which
	 * assembles the raw bytes into messages and passes them to the dispatcher.
	 */
	public void serialEvent(SerialPortEvent event) {
		switch (event.getEventType()) {
		case SerialPortEvent.BI:
		case SerialPortEvent.OE:
		case SerialPortEvent.FE:
		case SerialPortEvent.PE:
		case SerialPortEvent.CD:
		case SerialPortEvent.CTS:
		case SerialPortEvent.DSR:
		case SerialPortEvent.RI:
		case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
			break;
		case SerialPortEvent.DATA_AVAILABLE:
			try {
				if ((byte) inputStream.read() == KNOCK) {
					int expectedBytes = inputStream.read();
					ArrayList<Byte> bytes = new ArrayList<Byte>();

					for (int i = 0; i < expectedBytes; i++) {
						int received;
						while ((received = inputStream.read()) == -1) {
							// wait to receive the byte
						}
						bytes.add(Byte.valueOf((byte) received));
					}

					byte device = bytes.get(0);
					byte id = bytes.get(1);
					byte command = bytes.get(2);
					byte[] data = new byte[bytes.size() - 3];

					// data bytes start in the third index
					for (int i = 3; i < bytes.size(); i++) {
						data[i - 3] = bytes.get(i).byteValue();
					}

					Message m;

					m = new Message(device, id, command, data);
					dispatcher.dispatch(m);
				}
			} catch (IOException e) {
			} catch (InvalidArgumentsException e) {
				e.printStackTrace();
			} catch (InvalidFunctionException e) {
				e.printStackTrace();
			}

			break;
		}
	}

	/**
	 * Sends a single byte over the UART.
	 * 
	 * @param b
	 *            the byte to be sent
	 */
	private void send(byte b) {
		try {
			outputStream.write(b);
		} catch (IOException e) {
		}
	}

	/**
	 * Decomposes a message into bytes and sends it over the UART. The message
	 * will then be reassmled by the client and executed.
	 * 
	 * @param m
	 *            the message to be sent
	 */
	public void send(Message m) {
		send((byte) KNOCK); // send a verification byte
		send((byte) (3 + m.getData().length));
		send(m.getDevice().byteValue());
		send(m.getId());
		send(m.getFunction().byteValue());
		for (int i = 0; i < m.getData().length; i++) {
			send(m.getData()[i]);
		}
	}
}
